/**
 * Copyright (c) 2009 Netflix-Flex_API Contributors.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of 
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to 
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
 * of the Software, and to permit persons to whom the Software is furnished to do 
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. 
 * */
package com.netflix.webapis
{
	import com.netflix.webapis.events.NetflixFaultEvent;
	import com.netflix.webapis.events.NetflixResultEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	
	import mx.rpc.AsyncToken;
	
	import org.iotashan.oauth.IOAuthSignatureMethod;
	import org.iotashan.oauth.OAuthConsumer;
	import org.iotashan.oauth.OAuthSignatureMethod_HMAC_SHA1;
	import org.iotashan.oauth.OAuthToken;

	/**
	* Result Event.
	*/	
	[Event(name="result",type="com.netflix.webapis.events.NetflixResultEvent")]
	/**
	* Fault Event.
	*/	
	[Event(name="fault",type="com.netflix.webapis.events.NetflixFaultEvent")]
	
	/**
	 * Base to all service classes.
	 * @author jonbcampos
	 * 
	 */	
	public class ServiceBase extends EventDispatcher
	{
		public static const NETFLIX_BASE_URL:String = "http://api.netflix.com/";
		public static const SIG_METHOD:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
		
		/**
		 * @private
		 * Reference to service storage. 
		 */		
		private var storage:ServiceStorage;
		
		public function ServiceBase(target:IEventDispatcher=null)
		{
			super(target);
			storage = ServiceStorage.getInstance();
		}
		
		//---------------------
		// Key
		//---------------------
		[Bindable("keyChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Consumer Key. Get this variable from Netflix when you sign up for a developer account.
		 * 
		 * <p>Part of application wide storage.</p>
		 */
		public function get key():String
		{
			return this.storage.key;
		}
		
		public function set key(value:String):void
		{
			if(value && this.storage.secret){
				this.storage.consumer = new OAuthConsumer(value,this.storage.secret);
				dispatchEvent(new Event("consumerChanged"));
			}
			this.storage.key = value;
			dispatchEvent(new Event("keyChanged"));
		}
		
		//---------------------
		// Secret
		//---------------------
		[Bindable("secretChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Consumer Secret. Get this variable from Netflix when you sign up for a developer account.
		 * 
		 * <p>Part of application wide storage.</p>
		 */
		public function get secret():String
		{
			return this.storage.secret;
		}
		
		public function set secret(value:String):void
		{
			if(value && this.storage.key){
				this.storage.consumer = new OAuthConsumer(this.storage.key,value);
				dispatchEvent(new Event("consumerChanged"));
			}
			this.storage.secret = value;
			dispatchEvent(new Event("secretChanged"));
		}
		
		//---------------------
		// Consumer
		//---------------------
		[Bindable("secretChanged")]
		[Bindable("keyChanged")]
    	[Inspectable(category="General")]
    	/**
		 * OAuth Consumer created when the developer key and secret are entered into the authorization service.
		 * 
		 * <p>Part of application wide storage.</p>
		 */
		public function get consumer():OAuthConsumer
		{
			return this.storage.consumer;
		}
		
		//---------------------
		// Token
		//---------------------
		[Bindable("tokenChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Token returned from the <code>AccessTokenService.getAccessToken()</code> 
		 * and/or <code>AuthenticationService.requestToken()</code>.
		 * 
		 * <p>Part of application wide storage.</p>
		 * 
		 * @see com.netflix.webapis.authenticate.AuthenticationService#requestToken()
		 * @see com.netflix.webapis.authenticate.AccessTokenService#getAccessToken()
		 */	
		public function get token():String
		{
			return this.storage.token;
		}
		
		public function set token(value:String):void
		{
			this.storage.token = value;
			if(value && tokenSecret){
				this.storage.accessToken = new OAuthToken(token,tokenSecret);
				dispatchEvent(new Event("accessTokenChanged"));
			}
			dispatchEvent(new Event("tokenChanged"));
		}
		
		//---------------------
		// Token Secret
		//---------------------
		[Bindable("tokenSecretChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Token Secret returned from the <code>AccessTokenService.getAccessToken()</code> 
		 * and/or <code>AuthenticationService.requestToken()</code>. 
		 * 
		 * <p>Part of application wide storage.</p>
		 * 
		 * @see com.netflix.webapis.authenticate.AuthenticationService#requestToken()
		 * @see com.netflix.webapis.authenticate.AccessTokenService#getAccessToken()
		 */	
		public function get tokenSecret():String
		{
			return this.storage.tokenSecret;
		}
		
		public function set tokenSecret(value:String):void
		{
			this.storage.tokenSecret = value;
			if(value && token){
				this.storage.accessToken = new OAuthToken(token,tokenSecret);
				dispatchEvent(new Event("accessTokenChanged"));
			}
			dispatchEvent(new Event("tokenSecretChanged"));
		}
		
		//---------------------
		// AuthToken
		//---------------------
		[Bindable("accessTokenChanged")]
		[Bindable("tokenSecretChanged")]
    	[Inspectable(category="General")]
    	/**
		 * OAuth Token created when the token and tokenSecret are returned.
		 * 
		 * <p>Part of application wide storage.</p>
		 */	
		public function get accessToken():OAuthToken
		{
			return this.storage.accessToken;
		}
		
		//---------------------
		// User Id
		//---------------------
		[Bindable("userIdChanged")]
    	[Inspectable(category="General")]
    	/**
		 * User id returned from the AccessToken Service.
		 * 
		 * <p>Part of application wide storage.</p>
		 */	
		public function get userId():String
		{
			return this.storage.userId;
		}
		
		public function set userId(value:String):void
		{
			this.storage.userId = value;
			dispatchEvent(new Event("userIdChanged"));
		}
		
		//---------------------
		// applicationName
		//---------------------
		[Bindable("applicationNameChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Application Name returned from the <code>AuthenticationService.requestToken()</code> call.
		 * 
		 * <p>Part of application wide storage.</p>
		 * 
		 * @see com.netflix.webapis.authenticate.AuthenticationService#requestToken()
		 */		
		public function get applicationName():String
		{
			return this.storage.applicationName;
		}
		
		public function set applicationName(value:String):void
		{
			this.storage.applicationName = value;
			dispatchEvent(new Event("applicationNameChanged"));
		}
		
		//---------------------
		// loginURL
		//---------------------
		[Bindable("loginURLChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Login URL returned from the <code>AuthenticationService.requestToken()</code> call.
		 * 
		 * <p>Part of application wide storage.</p>
		 * 
		 * @see com.netflix.webapis.authenticate.AuthenticationService#requestToken()
		 */	
		public function get loginURL():String
		{
			return this.storage.loginURL;
		}
		
		public function set loginURL(value:String):void
		{
			this.storage.loginURL = value;
			dispatchEvent(new Event("loginURLChanged"));
		}
		
		//---------------------
		// authorizationURL
		//---------------------
		[Bindable("authorizationURLChanged")]
    	[Inspectable(category="General")]
    	/**
		 * Authentication URL returned from the <code>AuthenticationService.requestToken()</code> call. 
		 * This is what is used to authorize your application to a user for user information access.
		 * 
		 * <p>To have the user authorize your app to access their account you need to invoke the 
		 * <code>AuthenticationService.getAuthorization()</code> function. This will open up a new 
		 * window where they will authorize your app. This one needs to be done once or until the
		 * user removes your app from the authorized list.<br />
		 * <br />
		 * Part of application wide storage.
		 * </p>
		 * 
		 * @see com.netflix.webapis.authenticate.AuthenticationService#requestToken()
		 * @see com.netflix.webapis.authenticate.AuthenticationService#getAuthorization()
		 */
		public function get authorizationURL():String
		{
			return this.storage.authorizationURL;
		}
		
		public function set authorizationURL(value:String):void
		{
			this.storage.authorizationURL = value;
			dispatchEvent(new Event("authorizationURLChanged"));
		}
		
		//---------------------
		//
		// Requests Storage
		//
		//---------------------
		[Bindable]
		/**
		 * The total amount of results available 
		 */
		public var numberOfResults:uint;
		
		[Bindable]
		/**
		 * The amount of results of this page.
		 */
		public var numResultsReturned:uint;
		
		[Bindable]
		/**
		 * The total amount of results returned.
		 */
		public var resultsPerPage:uint;
		
		[Bindable]
		/**
		 * The position of the first result.
		 */
		public var firstResultPosition:uint;
		
		[Bindable]
		/**
		 * The position of the last result.
		 */
		public var lastResultPosition:uint;
		
		[Bindable]
		/**
		 * Whether to retrieve more results on the same search.
		 */
		public var autoIncrement:Boolean;
		
		[Bindable]
		/**
		* The result of the last service invocation.
		* */
		public var lastResult:Object = new Array();
		
		/**
		* The last search parameters sent.
		* @private
		*/
		private var previousRequest:String;
		
		private var previousType:String;
		
		/**
		 * Current Request. 
		 */		
		public var request:ParamsBase;
		[Bindable]
		public var startIndex:uint = 0;
		[Bindable]
		public var increment:uint = 0;
		[Bindable]
		public var hasResultsPending:Boolean = true;
		
		private var _maximumResults:uint = 10;
		[Bindable]
		public function get maximumResults():uint
		{
			return _maximumResults;
		}
		
		public function set maximumResults( value:uint ):void
		{
			if (value > 100) value = 100;
			_maximumResults = value;
		}
		
		//---------------------
		//
		// Methods
		//
		//---------------------
		/**
		 * Sends off for service.
		 * 
		 * This provides a way to use a standard method for services.
		 *  
		 * @param parameters
		 * 
		 */
		public function send(parameters:ParamsBase = null):void
		{
			if(parameters == null)
			{
				if(request == null)
					request = new ParamsBase();
			}
		}
		/**
		 * Handles requests and previous requests to determine current request. 
		 * @param params sent params.
		 * @param type Requested type.
		 * @return 
		 * 
		 */		
		protected function determineParams(params:ParamsBase=null,type:String=null):ParamsBase
		{
			if(params == null)
			{
				if(request == null)
					request = new ParamsBase();
			}
			if(params is ParamsBase)
				request = params as ParamsBase;
				
			var newRequest:String = request.toString();
			//take out the results and start parameters for comparison
			newRequest = newRequest.replace(/&start_index=\d+/,"");
			newRequest = newRequest.replace(/&max_results=\d+/,"");
			
			if((previousType == type) && (previousRequest == newRequest) && autoIncrement)
			{
				//continue to retrieve results for this same search
				increment ++;
				
			}
			else
			{
				//new query, so reset
				increment = 0;
				hasResultsPending = true;
				lastResult = new Array();
				
				//store the last query
				previousRequest = request.toString();
				previousType = type;
			}
			
			//continue retrieving results for this same query, if there are any more
			if(!hasResultsPending) return request;
			
			//Set the index to start at, based on paging (Netflix APIs are 1-based)
			request.startIndex = startIndex + ( increment * request.maxResults  ) + 1;
			
			return request;
		}
		/**
		 * Handlers the service loading and calls for the service. Constructs the query string and retrieves the 
		 * parameters based <code>params</code> and sends for the service. 
		 * @param methodString url to send to
		 * @param dispatchType what kind of result to dispatch
		 * @param params parameters of the query
		 * @param inDepth ? - possibly going to remove
		 * 
		 */
		protected function handleServiceLoading(methodString:String, dispatchType:String, params:ParamsBase = null):void
		{
		}
		/**
		 * Formats the xml into typed objects and dispatches the final object in a result event.
		 * @param returnedXML
		 * @param dispatchType
		 * 
		 */	
		protected function formatAndDispatch(returnedXML:XML, dispatchType:String):void
		{
			if(increment == 0) lastResult = new Array();
			
			var len:int = lastResult.length;
			numberOfResults = (returnedXML.number_of_results)?returnedXML.number_of_results:len;
			startIndex = (returnedXML.start_index)?returnedXML.start_index:0;
			resultsPerPage = (returnedXML.results_per_page)?returnedXML.results_per_page:len;
		}
		/**
		 * Dispatches the result. 
		 * @param result formatted data
		 * @param dispatchType
		 * 
		 */	
		protected function dispatchResult(result:Object, dispatchType:String, rawXML:XML = null):void
		{
			dispatchEvent(new NetflixResultEvent(NetflixResultEvent.RESULT, result, dispatchType, rawXML));
		}
		/**
		 * Dispatches Fault.
		 * @param fault
		 * 
		 */	
		protected function dispatchFault(fault:ServiceFault):void
		{
			dispatchEvent(new NetflixFaultEvent(NetflixFaultEvent.FAULT,fault));
		}
	}
}